import { Metadata } from "next"; import kairosImage from "../kairos.png"; import { createClient } from "@libsql/client"; import Link from 'next/link'; interface NewsData { identifier: string; type: string | null; timestamp: number; headline: string | null; content: string; url: string | null; images: Array<{ image: string; link: string | null; }>; en_headline: string | null; en_content: string | null; is_ai_summary: boolean | null; } export async function generateMetadata({ params, searchParams, }: { params: Promise<{ gameName?: string }>; searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }): Promise { const resolvedParams = await params; const resolvedSearchParams = await searchParams; let gameName = resolvedParams.gameName || "news"; const postId = resolvedSearchParams.post as string | undefined; const lang = resolvedSearchParams.lang as string | undefined; const apiUrlBase = process.env.NEXT_PUBLIC_API_URL; if (!postId) { return { title: `${gameName} News`, description: `Browse the latest updates for ${gameName}`, }; } try { let fetchUrl = `${apiUrlBase}/${gameName}_news.json`; if (gameName === "news") { fetchUrl = `${apiUrlBase}/news.json`; } const res = await fetch(fetchUrl); if (!res.ok) throw new Error("Failed to fetch"); const data = await res.json(); const newsPosts = data.news_posts; const matchingPost = newsPosts.find((news: any) => { const contentHash = news.content.split("").reduce((hash: number, char: string) => { return (hash << 5) + hash + char.charCodeAt(0); }, 5381) >>> 0; const headlineHash = (news.headline || "null") .split("") .reduce( (hash: number, char: string) => (hash << 5) + hash + char.charCodeAt(0), 5381, ) >>> 0; const newsId = `${news.identifier}-${news.timestamp}-${contentHash.toString(16)}-${headlineHash.toString(16)}`; return newsId === postId; }); if (!matchingPost) { try { const client = createClient({ url: process.env.SQLITE_DB!, authToken: process.env.REMOTE_AUTH_TOKEN!, }); const result = await client.execute({ sql: `SELECT news_id, date, identifier, type, timestamp, headline, content, url, is_ai_summary, en_headline, en_content FROM news WHERE news_id = ?`, args: [postId], }); if (result.rows.length === 0) { return { title: "Post not found" }; } const row = result.rows[0]; const imagesResult = await client.execute({ sql: `SELECT image_url, link_url FROM news_images WHERE news_id = ?`, args: [postId], }); const images = imagesResult.rows.map((img) => ({ image: img.image_url, link: img.link_url, })); const dbPost = { news_id: row.news_id, date: row.date, identifier: row.identifier, type: row.type, timestamp: row.timestamp, headline: row.headline, content: row.content, url: row.url, is_ai_summary: Boolean(row.is_ai_summary), en_headline: row.en_headline, en_content: row.en_content, images, }; if (lang === "en") { if (dbPost.en_headline !== null) { dbPost.headline = dbPost.en_headline; } if (dbPost.en_content !== null) { dbPost.content = dbPost.en_content; } } if (!dbPost.headline) { dbPost.headline = dbPost.content; } return { title: String(dbPost.headline || "Untitled"), description: String(dbPost.content || "").slice(0, 300), openGraph: { title: String(dbPost.headline || "Untitled"), description: String(dbPost.content || "").slice(0, 300), images: dbPost.images?.[0]?.image ? [String(dbPost.images[0].image)] : [], }, }; } catch (dbErr) { console.error("Database fallback error:", dbErr); return { title: "Post not found" }; } } if (lang === "en") { if (matchingPost.en_headline !== null) { matchingPost.headline = matchingPost.en_headline; } if (matchingPost.en_content !== null) { matchingPost.content = matchingPost.en_content; } } if (!matchingPost.headline) { matchingPost.headline = matchingPost.content; } return { title: matchingPost.headline, description: matchingPost.content.slice(0, 300), openGraph: { title: matchingPost.headline, description: matchingPost.content.slice(0, 300), images: matchingPost.images?.[0]?.image ? [matchingPost.images[0].image] : [], }, }; } catch (err) { console.error(err); return { title: "Error loading post", description: "There was a problem loading this news post.", }; } } export default async function GamePage({ params, searchParams, }: { params: Promise<{ gameName?: string }>; searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const resolvedParams = await params; const resolvedSearchParams = await searchParams; const gameName = resolvedParams.gameName || "news"; const postId = resolvedSearchParams.post as string | undefined; const lang = resolvedSearchParams.lang as string | undefined; const mainNewsUrl = process.env.NEXT_PUBLIC_MAIN_NEWS_URL; const apiUrlBase = process.env.NEXT_PUBLIC_API_URL; if (postId) { let newsPost: NewsData | null = null; try { let fetchUrl = `${apiUrlBase}/${gameName}_news.json`; if (gameName === "news") { fetchUrl = `${apiUrlBase}/news.json`; } const res = await fetch(fetchUrl); if (res.ok) { const data = await res.json(); const newsPosts = data.news_posts; const matchingPost = newsPosts.find((news: any) => { const contentHash = news.content.split("").reduce((hash: number, char: string) => { return (hash << 5) + hash + char.charCodeAt(0); }, 5381) >>> 0; const headlineHash = (news.headline || "null") .split("") .reduce( (hash: number, char: string) => (hash << 5) + hash + char.charCodeAt(0), 5381, ) >>> 0; const newsId = `${news.identifier}-${news.timestamp}-${contentHash.toString(16)}-${headlineHash.toString(16)}`; return newsId === postId; }); if (matchingPost) { newsPost = matchingPost; } } // If not found in JSON, try database if (!newsPost) { const client = createClient({ url: process.env.SQLITE_DB!, authToken: process.env.REMOTE_AUTH_TOKEN!, }); const result = await client.execute({ sql: `SELECT news_id, date, identifier, type, timestamp, headline, content, url, is_ai_summary, en_headline, en_content FROM news WHERE news_id = ?`, args: [postId], }); if (result.rows.length > 0) { const row = result.rows[0]; // Get images for this news post const imagesResult = await client.execute({ sql: `SELECT image_url, link_url FROM news_images WHERE news_id = ?`, args: [postId], }); const images = imagesResult.rows.map((img) => ({ image: img.image_url, link: img.link_url, })); newsPost = { identifier: row.identifier as string, type: row.type as string | null, timestamp: row.timestamp as number, headline: row.headline as string | null, content: row.content as string, url: row.url as string | null, is_ai_summary: Boolean(row.is_ai_summary), en_headline: row.en_headline as string | null, en_content: row.en_content as string | null, images: images.map(img => ({ image: String(img.image), link: img.link ? String(img.link) : null })), }; } } } catch (err) { console.error("Error fetching news post:", err); } // If we found the post, render it if (newsPost) { return ( ); } } // Default fallback page const redirectUrl = postId && mainNewsUrl ? gameName === "news" ? `${mainNewsUrl}/#${postId}` : `${mainNewsUrl}/game/${gameName}#${postId}` : mainNewsUrl; return (

573 UPDATES

Updates image {postId && !redirectUrl && (

Post not found

)} {redirectUrl && ( <>
click here if not redirected )}
); } // Component to render a single news post function NewsPostPage({ newsPost, lang, gameName, postId, mainNewsUrl, }: { newsPost: NewsData; lang?: string; gameName: string; postId: string; mainNewsUrl?: string; }) { let displayHeadline = newsPost.headline; let displayContent = newsPost.content; if (lang === "en") { if (newsPost.en_headline !== null) { displayHeadline = newsPost.en_headline; } if (newsPost.en_content !== null) { displayContent = newsPost.en_content; } } if (!displayHeadline) { displayHeadline = displayContent; } const date = new Date(newsPost.timestamp * 1000).toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit", }); const redirectUrl = mainNewsUrl ? gameName === "news" ? `${mainNewsUrl}/#${postId}` : `${mainNewsUrl}/game/${gameName}#${postId}` : null; const createToggleUrl = () => { const params = new URLSearchParams(); params.set('post', postId); if (lang === 'en') { } else { params.set('lang', 'en'); } const baseUrl = gameName === "news" ? "/news" : `/game/${gameName}`; const queryString = params.toString(); return queryString ? `${baseUrl}?${queryString}` : baseUrl; }; return (
{/* Post Header */}
{date}
{newsPost.type && (
{newsPost.type}
)}
{/* Content */}
{displayHeadline && (

{displayHeadline}

)}
{displayContent .split(/(\[.*?\]\(.*?\)|https?:\/\/[^\s]+)/g) .map((part, idx) => { const linkMatch = part.match(/\[(.*?)\]\((.*?)\)/); const urlMatch = part.match(/https?:\/\/[^\s]+/); if (linkMatch) { return ( {linkMatch[1]} ); } if (urlMatch) { return ( {urlMatch[0]} ); } return ( {part} ); })}
{/* AI Disclaimer */} {newsPost.is_ai_summary && (
This content was generated using AI and may contain inaccuracies
)} {/* Machine Translation Disclaimer */} {(newsPost.en_headline || newsPost.en_content) && lang === "en" && (
This is a machine translation and may contain errors
)} {/* Images */} {newsPost.images && newsPost.images.length > 0 && (
News visual
)} {/* Read More Link */} {newsPost.url && (
READ MORE
)}
{/* About 573 UPDATES */}

This is a perma-link hosted on 573 UPDATES

A news aggregator for some arcade (and some not-so arcade) games. Image data is loaded from external sources, and as such may not always be available.

{/* Navigation Buttons */}
{/* Language Toggle Button */} {(newsPost.en_headline || newsPost.en_content) && ( {lang === "en" ? "日本語で読む" : "Read in English"} )} {lang === "en" ? "Back to 573 UPDATES" : "573 UPDATESに戻る"}
); }